use regex::Regex;

pub fn convert_base(num_str: &str, to_base: u32) -> String {
    const SYMBOL_TABLE: [char; 15] = [
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e',
    ];

    fn matching_number_and_decimal_str(num_str: &str) -> (&str, &str) {
        let number_err_msg =
            "Invalid number: The numeric string must conform to the format requirements.";

        let re = Regex::new(r"^([0-9a-eA-E]+)\(([2-9]+|1[0-6])\)$").unwrap();
        let caps = re.captures(num_str).expect(number_err_msg);

        let number_str = caps.get(1).expect(number_err_msg).as_str();
        let decimal_str = caps.get(2).expect(number_err_msg).as_str();

        (number_str, decimal_str)
    }

    fn parse_decimal(decimal_str: &str) -> u32 {
        let decimal = decimal_str
            .parse::<u32>()
            .expect("Invalid base: Base must be a number string, such as 2, 5, 10, 16.");

        if decimal < 2 || decimal > 16 {
            panic!("Invalid base: Base must be between 2 and 16.")
        }

        decimal
    }

    fn parse_number(number_str: &str, decimal: u32) -> u32 {
        if decimal < 2 || decimal > 16 {
            panic!("Invalid base: Base must be between 2 and 16.")
        }

        let mut number_vec: Vec<u32> = Vec::new();

        for digit in number_str.to_lowercase().chars() {
            let index = SYMBOL_TABLE
                .iter()
                .position(|&x| x == digit)
                .expect("Invalid number: Number contains invalid characters.")
                as u32;

            if index >= decimal.into() {
                panic!("Invalid number: Number exceeds the base.");
            }

            number_vec.push(index);
        }

        number_vec.reverse();

        let mut number_position: u32 = 0;
        let mut num: u32 = 0;
        for digit in number_vec.into_iter() {
            num += decimal.pow(number_position) * digit;
            number_position += 1;
        }

        num
    }

    fn base_conversion(num: u32, decimal: u32) -> String {
        let mut digit_vec: Vec<char> = Vec::new();
        let mut quotient = num;

        while quotient >= decimal {
            let remainder = quotient % decimal;
            digit_vec.push(*(SYMBOL_TABLE.get(remainder as usize).expect("Character overflow in number")));
            quotient = (quotient - remainder) / decimal;
        }

        if quotient != 0 || digit_vec.len() == 0 {
            digit_vec.push(*(SYMBOL_TABLE.get(quotient as usize).expect("Character overflow in number")));
        }

        digit_vec.reverse();

        digit_vec.iter().collect()
    }

    let (number_str, decimal_str) = matching_number_and_decimal_str(num_str);

    base_conversion(
        parse_number(&number_str, parse_decimal(decimal_str)),
        to_base,
    )
}
